<template>
	<view class="v-form" :class="[layout]">
		<slot></slot>
		<slot name="operate" :handleSubmit="handleSubmit" :handleRest="handleRest" :buttonDisabled="submitting" :buttonSize="buttonSize">
			<view class="form-actions">
				<v-button :disabled="submitting" @click="handleSubmit" :size="buttonSize">
					<text v-if="!submitting">{{ submitText }}</text>
					<text v-else>提交中...</text>
				</v-button>
				<v-button type="info" @click="handleRest" :size="buttonSize" v-if="showRest">重置</v-button>
			</view>
		</slot>
	</view>
</template>

<script lang="ts" setup>
import { ref, provide, reactive, watchEffect, inject } from 'vue';

type FormItem = {
	validate: () => Promise<boolean>;
	resetField: () => void;
};

interface Rules {
	required?: boolean;
	message?: string;
	pattern?: RegExp;
	minLength?: number;
	maxLength?: number;
	validator?: (value: any) => boolean | Promise<boolean>;
	compareWith?: string;
	min?: number;
	max?: number;
	type?: 'email' | 'mobile' | 'number' | 'integer';
}

/**
 * v-form 表单
 * model		表单数据对象
 * rules		验证规则	{ field: [{ required: true, message: '提示' }]}
 * submitText	提交按钮文字
 * layout		表单布局方式	默认值: vertical  	可选值: vertical垂直排列 | horizontal水平排列
 * labelLayout	标签对齐方式	默认值:	top			可选值:
 * labelPosition标签垂直方式 默认值: normal		可选值:center
 * buttonSize	按钮大小		默认值:	medium		可选值:small小medium中large大
 * showRest		是否显示重置按钮		默认值true	可选值:true显示 false不显示
 * disabled		是否禁用表单			默认值false	可选值:true禁用 false不禁用
 * 相关事件:validate	校验、submit提交、rest重置
 */

const props = defineProps({
	model: {
		type: Object,
		required: true,
		default: () => ({})
	},
	rules: {
		type: Object as () => Rules,
		default: () => ({})
	},
	submitText: {
		type: String,
		default: '提交'
	},
	layout: {
		type: String,
		default: 'vertical',
		validator: (v: string) => ['vertical', 'horizontal'].includes(v)
	},
	buttonSize: {
		type: String,
		default: 'medium',
		validator: (v: string) => ['small', 'medium', 'large'].includes(v)
	},
	showRest: {
		type: Boolean,
		default: true
	},
	disabled: {
		type: Boolean,
		default: false
	},
	labelWidth: {
		type: String,
		default: '180'
	},
	labelLayout: {
		type: String,
		default: 'top'
	},
	labelLast: {
		type: String,
		default: 'auto'
	},
	labelPosition: {
		type: String,
		default: 'normal'
	},
	labelSpacing: {
		type: Number,
		default: 0
	}
});

const emit = defineEmits(['submit', 'validate', 'rest']);

const formHasError = ref(false);
const config = inject<any>('config');
const formItems = new Map<string, FormItem>();
const submitting = ref(false);
const formData = reactive({ ...props.model });

provide('form', {
	rules: props.rules || {},
	formData,
	register: (name: string, item: FormItem) => {
		formItems.set(name, item);
	},
	unregister: (name: string) => {
		formItems.delete(name);
	},
	formHasError:formHasError
});

const handleSubmit = async () => {
	submitting.value = true;
	formHasError.value = false;
	try {
		const results = await Promise.all(Array.from(formItems.values()).map((item) => item.validate()));
		const isValid = results.every((valid) => valid);

		emit('validate', isValid);
		if (isValid) {
			emit('submit', formData);
		} else {
			formHasError.value = true;
		}
	} finally {
		submitting.value = false;
	}
};

const handleRest = () => {
	emit('rest', formData);
};

const resetForm = () => {
	formItems.forEach((item) => item.resetField());
};

defineExpose({
	resetForm,
	validate: handleSubmit
});

watchEffect(() => {
	Object.assign(formData, props.model);
});
</script>

<style lang="scss">
.v-form {
	$gap: 20rpx;
	$error-color: v-bind('config.fontColor.delete');

	&.horizontal {
		:deep(.form-item) {
			display: flex;
			flex-direction: row;

			.label {
				margin-right: $gap;
			}
		}
	}

	:deep(.form-item) {
		margin-bottom: $gap;
		.title {
			width: v-bind("props.labelWidth + 'rpx'");
			display: flex;
			align-items: v-bind('props.labelPosition');

			.label {
				width: 80%;
				font-size: v-bind('config.fontSize.largeText');
				color: v-bind('config.fontColor.subTitle');
				text-align: v-bind('props.labelLayout');
				text-align-last: v-bind('props.labelLast');
				letter-spacing: v-bind("props.labelSpacing + 'rpx'");
			}
			.required {
				width: 20%;
				height: 100%;
				.v-text {
					color: $error-color;
					margin-left: 8rpx;
					height: 100%;
					display: flex !important;
					align-items: v-bind('props.labelPosition');
				}
			}
		}
		.control {
			min-width: 50%;
		}

		.error-message {
			width: 100%;
			height: 40rpx;
			margin-top: 8rpx;
			.v-text {
				color: $error-color;
				font-size: v-bind('config.fontSize.mediumText');
			}
		}
	}

	.form-actions {
		margin-top: 40rpx;
		.v-button {
			width: 100%;
		}
	}
}
</style>
